/*
 * Copyright (c) 1990, 2022, shwfed and/or its affiliates. All rights reserved.
 *
 * @date: 2022-08-24
 *
 */
package cn.junglead.common.utils.cipher;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.Objects;

/**
 * 用于常见的AES和DES密码学工具的基类
 *
 * @author Gary.Wang
 * @version 1.0
 */
public abstract class BaseCipherUtil {

	protected BaseCipherUtil(){}

	/**
	 * 不支持偏移向量的算法
	 */
	protected static final String NOT_SUPPORT_IV = "/ECB/";

    /**
     * 输出格式为 BASE64
     */
    public static final int FORMAT_BASE64 = 1;

    /**
     * 输出格式为 HEX
     */
    public static final int FORMAT_HEX = 2;

	/**
	 * 创建不包含偏移向量的密码对象
	 * @param algorithms 密码算法
	 * @param opMode 操作模式，即解密还是加密。可选值： Cipher.ENCRYPT_MODE 或者 Cipher.DECRYPT_MODE 。
	 * @param key 秘钥对象
	 * @return 返回密码对象
	 * @throws NoSuchAlgorithmException 不存在的算法异常
	 * @throws NoSuchPaddingException 不存在的填充模式异常
	 * @throws InvalidKeyException 错误的密钥异常
	 * @throws InvalidAlgorithmParameterException 错误的算法参数异常
	 */
	protected static Cipher createCipher(String algorithms, int opMode, Key key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
		return createCipher(algorithms, opMode, key, null);

	}

	/**
	 * 创建指定偏移向量的密码对象
	 * @param algorithms 算法
	 * @param opMode 操作模式，即解密还是加密。可选值： Cipher.ENCRYPT_MODE 或者 Cipher.DECRYPT_MODE 。
	 * @param key 秘钥对象
	 * @param iv 偏移向量
	 * @return 返回密码对象
	 * @throws NoSuchAlgorithmException 不存在的算法异常
	 * @throws NoSuchPaddingException 不存在的填充模式异常
	 * @throws InvalidKeyException 错误的密钥异常
	 * @throws InvalidAlgorithmParameterException 错误的算法参数异常
	 */
	protected static Cipher createCipher(String algorithms, int opMode, Key key, IvParameterSpec iv) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
		Cipher cipher = Cipher.getInstance(algorithms);
		if (Objects.nonNull(iv) && !algorithms.contains(NOT_SUPPORT_IV)){
			cipher.init(opMode, key, iv);
		} else {
			cipher.init(opMode, key);
		}
		return cipher;
	}

	/**
	 * 根据算法及指定秘钥文本，生成对称算法（如 AES 以及 DES）的秘钥
	 * @param algorithms 算法
	 * @param key 秘钥
	 * @return 返回秘钥对象
	 */
	protected static SecretKeySpec generateKeygen(String algorithms, String key){
		return new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithms);
	}

	/**
	 * 根据指定算法及长度，生成对称算法（如 AES 以及 DES）的随机秘钥
	 * @param algorithms 算法
	 * @param keyLength 密钥长度，默认为128
	 * @return 返回Key对象
	 *
	 * @throws NoSuchAlgorithmException 不存在的算法异常
	 */
	protected static SecretKey generateRandomKeygen(String algorithms, int keyLength) throws NoSuchAlgorithmException {
		KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithms);
		keyGenerator.init(keyLength);
		return keyGenerator.generateKey();
	}

	/**
	 * 转换为明文文本
	 * @param outputFormat 输出格式是base64还是16进制，可选值： FORMAT_BASE64 或者 FORMAT_HEX
	 * @param byteContent 密文字节数组
	 * @return 返回明文文本
	 */
	protected static String toPlainString(int outputFormat, byte[] byteContent){
		if (outputFormat == FORMAT_HEX) {
			return Hex.encodeHexString(byteContent);
		}
		return Base64.getEncoder().encodeToString(byteContent);
	}

	/**
	 * 将文本密文转换为密文字节
	 * @param inputFormat 输入格式是base64还是16进制，可选值： FORMAT_BASE64 或者 FORMAT_HEX
	 * @param ciphertext 密文文本
	 * @return 返回密文字节
	 */
	protected static byte[] toCipherByte(int inputFormat, String ciphertext){
		byte[] input;
		if (inputFormat == FORMAT_HEX) {
			try {
				input = Hex.decodeHex(ciphertext.toCharArray());
			} catch (DecoderException e) {
				input = new byte[0];
			}
		} else {
			input = Base64.getDecoder().decode(ciphertext);
		}
		return input;
	}

	/**
	 * 随机偏移向量
	 * @param size 数组大小，一般与算法有关
	 * @return 返回偏移向量
	 */
	protected static IvParameterSpec randomIv(int size) {
		byte[] ivBytes = new byte[size];
		new SecureRandom().nextBytes(ivBytes);
		return new IvParameterSpec(ivBytes);
	}
}
